﻿//
// Copyright (c) 2024 BPer
//

#include <bperutil/sys/BPerSocket.h>
#include <bperutil/sys/BPerLog.h>

#if BPer_OS_WIN
#include <winsock2.h>
#include <ws2tcpip.h>

#define bPer_poll WSAPoll
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#endif

#include <fcntl.h>

#define bPer_sockaddr(x) x->familyType==BPer_IpFamilyType_IPV4?(const struct sockaddr*)(&x->addr4):(const struct sockaddr*)(&x->addr6)
#define bPer_sockaddr2(x) x->familyType==BPer_IpFamilyType_IPV4?(struct sockaddr*)(&x->addr4):(struct sockaddr*)(&x->addr6)
#define bPer_sockaddr_len(x)  x->familyType==BPer_IpFamilyType_IPV4?sizeof(bPer_socket_addr4):sizeof(bPer_socket_addr6);

void bPer_addr_set(BPerIpAddress* addr,char* ip,int32_t port,BPerIpFamilyType familyType, BPerSocketProtocol protocol){
	if(addr==NULL) return;
	addr->port=port;
	addr->familyType=familyType;
	addr->protocol=protocol;
	bPer_memcpy(addr->address,ip,bPer_strlen(ip));
	if(familyType==BPer_IpFamilyType_IPV4){
		addr->addr4.sin_family = AF_INET;
		addr->addr4.sin_port = bPer_htons(addr->port);
#if BPer_OS_WIN
		addr->addr4.sin_addr.S_un.S_addr=bPer_inet_addr(ip);
#else
		addr->addr4.sin_addr.s_addr = bPer_inet_addr(ip);
#endif
	}else{
		addr->addr6.sin6_family = AF_INET6;
		addr->addr6.sin6_port = bPer_htons(addr->port);
		bPer_inet_pton(AF_INET6, ip, &addr->addr6.sin6_addr);

	}
}

void bPer_addr_setAnyAddr(BPerIpAddress* addr,int32_t port,BPerIpFamilyType familyType, BPerSocketProtocol protocol){
	if(familyType==BPer_IpFamilyType_IPV6){
		uint8_t ip[16]={0};
		bPer_addr_setIPV6(addr,ip,port,protocol);
		return;
	}

		bPer_addr_setIPV4(addr,0,port,protocol);

}

void bPer_addr_setIPV4(BPerIpAddress* addr,int32_t ip,int32_t port,BPerSocketProtocol protocol){
	if(addr==NULL) return;
	addr->port=port;
	addr->familyType=BPer_IpFamilyType_IPV4;
	addr->protocol=protocol;


		addr->addr4.sin_family = AF_INET;
		addr->addr4.sin_port = bPer_htons(addr->port);
#if BPer_OS_WIN
		addr->addr4.sin_addr.S_un.S_addr=ip;
#else
		addr->addr4.sin_addr.s_addr = ip;
#endif

}


void bPer_addr_setIPV6(BPerIpAddress* addr,uint8_t ip[16],int32_t port,BPerSocketProtocol protocol){
	if(addr==NULL) return;
	addr->port=port;
	addr->familyType=BPer_IpFamilyType_IPV6;
	addr->protocol=protocol;
	addr->addr6.sin6_family = AF_INET6;
	addr->addr6.sin6_port = bPer_htons(addr->port);
	bPer_memcpy(&addr->addr6.sin6_addr,ip,16);

}

void bPer_addr_getIPStr(BPerIpAddress* addr,char* addrstr,int32_t strLen){
	if(addr->familyType == BPer_IpFamilyType_IPV4 )
		inet_ntop(AF_INET,&addr->addr4.sin_addr.s_addr, addrstr, strLen);
	else
        inet_ntop(AF_INET6,&addr->addr6.sin6_addr, addrstr, strLen);
}

uint32_t bPer_addr_getIP(BPerIpAddress* addr){
#if BPer_OS_WIN
	return addr->addr4.sin_addr.S_un.S_addr;
#else
	return addr->addr4.sin_addr.s_addr;
#endif

}

uint16_t bPer_addr_getPort(BPerIpAddress* addr){
	return bPer_htons(addr->familyType == BPer_IpFamilyType_IPV4?addr->addr4.sin_port:addr->addr6.sin6_port);
}

uint16_t bPer_addr_getSinPort(BPerIpAddress* addr){
	return addr->familyType == BPer_IpFamilyType_IPV4?addr->addr4.sin_port:addr->addr6.sin6_port;
}

bPer_socket_t bPer_socket_create(BPerIpFamilyType familyType, BPerSocketProtocol protocol){
#if BPer_OS_WIN
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(2, 2);
    WSAStartup(wVersionRequested, &wsaData);
#endif

    int32_t fd=-1;
	fd = socket(familyType == BPer_IpFamilyType_IPV4 ? AF_INET : AF_INET6, protocol==BPer_Socket_Protocol_Tcp?SOCK_STREAM:SOCK_DGRAM, 0);

	if(fd==-1){
		bPer_error("create socket error: %d ", GetSockError());
		return fd;
	}

	int32_t timeoutMs=800;
#if BPer_OS_WIN
    int32_t timeout=timeoutMs;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &timeout,  sizeof(timeout));
#else
	struct timeval tv;
	tv.tv_sec = 0;
    tv.tv_usec = timeoutMs*1000;  //  ms
	setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv,	sizeof(struct timeval));
	int value = 1;
	setsockopt(fd, SOL_SOCKET, BPER_NO_SIGNAL, &value, sizeof(value));
#endif

	if(protocol==BPer_Socket_Protocol_Tcp){
		//int sendBufSize=32*1024;
		//setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendBufSize, sizeof(sendBufSize));

		int32_t on = 1;
		setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*) &on, sizeof(on));
	}

	return fd;

}
int32_t bPer_socket_setNonblock(bPer_socket_t fd) {
#if BPer_OS_WIN
	if (fd != INVALID_SOCKET) {
		int iMode = 1;
		ioctlsocket(fd, FIONBIO,  & iMode);
	}
#else
	if (fd != -1) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
#endif
	return BPer_Ok;
}



int32_t bPer_socket_close(bPer_socket_t fd){
#if BPer_OS_WIN
	closesocket(fd);
#else
	close(fd);
#endif

	return BPer_Ok;
}

int32_t bPer_socket_listen(bPer_socket_t fd,BPerIpAddress* addr){
	int32_t err=BPer_Ok;
	socklen_t addrLen=bPer_sockaddr_len(addr);
	if((err=bind(fd, bPer_sockaddr(addr),addrLen))!=BPer_Ok){
		return bPer_error_wrap(err,"socket bind error");
	}
	if(addr->protocol==BPer_Socket_Protocol_Tcp){
		return listen(fd,5);
	}
	return BPer_Ok;

}



int32_t bPer_socket_connect(bPer_socket_t fd,BPerIpAddress* remoteAddr){
	socklen_t addrLen=bPer_sockaddr_len(remoteAddr);
	return connect(fd,bPer_sockaddr(remoteAddr),addrLen);
}

int32_t bPer_socket_recvfrom(bPer_socket_t fd,char* buffer,int32_t bufferLen,BPerIpAddress* addr){
	socklen_t srcLen=bPer_sockaddr_len(addr);
	return recvfrom(fd, buffer, bufferLen, 0,	bPer_sockaddr2(addr), &srcLen);
}

int32_t bPer_socket_sendto(bPer_socket_t fd,char* data,int32_t nb,BPerIpAddress* remote_addr,int32_t flag){
	socklen_t addrLen=bPer_sockaddr_len(remote_addr);
	return sendto(fd, data, nb, flag, bPer_sockaddr(remote_addr),addrLen);
}

int32_t bPer_socket_send(bPer_socket_t fd,char* data,int32_t nb){
	return send(fd, data, nb, BPER_NO_SIGNAL);
}

int32_t bPer_socket_send2(bPer_socket_t fd,char* data,int32_t nb,int32_t flag){
	return send(fd, data, nb, flag);
}

int32_t bPer_socket_recv(bPer_socket_t fd,char* data,int32_t nb,int32_t flag){
	return recv(fd, data, nb, flag);
}

int32_t bPer_socket_accept(bPer_socket_t fd,BPerIpAddress* addr){
	socklen_t addrLen=bPer_sockaddr_len(addr);
	return accept(fd, bPer_sockaddr2(addr), &addrLen);
}

